Web services are all about providing a web API onto your web application and are typically implemented in either
SOAP or
REST.
REST is not really a technology in itself, but more an architectural pattern. REST is extremely simple and just involves using plain XML or JSON as a communication medium, combined with URL patterns that are "representational" of the underlying system and HTTP methods such as GET, PUT, POST and DELETE.
Each HTTP method maps to an action. For example GET for retrieving data, PUT for creating data, POST for updating and so on. In this sense REST fits quite well with
CRUD.
URL patterns
The first step to implementing REST with Grails is to provide RESTful
URL mappings:
static mappings = {
"/product/$id?"(resource:"product")
}
What this does is map the URI
/product
onto a
ProductController
. Each HTTP method such as GET, PUT, POST and DELETE map to unique actions within the controller as outlined by the table below:
Method | Action |
---|
GET | show |
PUT | update |
POST | save |
DELETE | delete |
You can alter how HTTP methods by using the capability of URL Mappings to
map to HTTP methods:
"/product/$id"(controller:"product"){
action = [GET:"show", PUT:"update", DELETE:"delete", POST:"save"]
}
However, unlike the
resource
argument used previously, in this case Grails will not provide automatic XML or JSON marshaling for you unless you specify the
parseRequest
argument in the URL mapping:
"/product/$id"(controller:"product", parseRequest:true){
action = [GET:"show", PUT:"update", DELETE:"delete", POST:"save"]
}
HTTP Methods
In the previous section you saw how you can easily define URL mappings that map specific HTTP methods onto specific controller actions. Writing a REST client that then sends a specific HTTP method is then trivial (example in Groovy's HTTPBuilder module):
import groovyx.net.http.*
import static groovyx.net.http.ContentType.JSONdef http = new HTTPBuilder("http://localhost:8080/amazon") http.request(Method.GET, JSON) {
url.path = '/book/list'
response.success = {resp, json ->
json.books.each { book ->
println book.title
}
}
}
However, issuing a request with a method other than
GET
or
POST
from a regular browser is not possible without some help from Grails. When defining a
form you can specify an alternative method such as
DELETE
:
<g:form controller="book" method="DELETE">
..
</g:form>
Grails will send a hidden parameter called
_method
, which will be used as the request's HTTP method. Another alternative for changing the method for non-browser clients is to use the
X-HTTP-Method-Override
to specify the alternative method name.
XML Marshaling - Reading
The controller implementation itself can use Grails'
XML marshaling support to implement the GET method:
import grails.converters.*
class ProductController {
def show = {
if(params.id && Product.exists(params.id)) {
def p = Product.findByName(params.id)
render p as XML
}
else {
def all = Product.list()
render all as XML
}
}
..
}
Here what we do is if there is an
id
we search for the
Product
by name and return it otherwise we return all Products. This way if we go to
/products
we get all products, otherwise if we go to
/product/MacBook
we only get a MacBook.
XML Marshalling - Updating
To support updates such as
PUT
and
POST
you can use the
params object which Grails enhances with the ability to read an incoming XML packet. Given an incoming XML packet of:
<?xml version="1.0" encoding="ISO-8859-1"?>
<product>
<name>MacBook</name>
<vendor id="12">
<name>Apple</name>
</vender>
</product>
You can read this XML packet using the same techniques described in the
Data Binding section via the
params object:
def save = {
def p = new Product(params['product']) if(p.save()) {
render p as XML
}
else {
render p.errors
}
}
In this example by indexing into the
params
object using the key
'product'
we can automatically create and bind the XML using the constructor of the
Product
class. An interesting aspect of the line:
def p = new Product(params['product'])
Is that it requires no code changes to deal with a form submission that submits form data than it does to deal with an XML request. The exact same technique can be used with a JSON request too.
If you require different responses to different clients (REST, HTML etc.) you can use content negotation
The
Product
object is then saved and rendered as XML, otherwise an error message is produced using Grails'
validation capabilities in the form:
<error>
<message>The property 'title' of class 'Person' must be specified</message>
</error>
There are several plugins that add SOAP support to Grails depending on your preferred approach. For Contract First SOAP services there is a
Spring WS plugin, whilst if you want to generate a SOAP API from Grails services there are several plugins that do this including:
Most of the SOAP integrations integrate with Grails
services via the
exposes
static property. The below example is taken from the XFire plugin:
class BookService { static expose=['xfire'] Book[] getBooks(){
Book.list() as Book[]
}
}
The WSDL can then be accessed at the location:
http://127.0.0.1:8080/your_grails_app/services/book?wsdl
For more information on the XFire plug-in refer
the documentation on the wiki.
No direct support is provided for RSS or Atom within Grails. You could construct RSS or ATOM feeds with the
render method's XML capability. There is however a
Feeds plug-in available for Grails that provides a RSS and Atom builder using the popular
ROME library. An example of its usage can be seen below:
def feed = {
render(feedType:"rss", feedVersion:"2.0") {
title = "My test feed"
link = "http://your.test.server/yourController/feed" Article.list().each() {
entry(it.title) {
link = "http://your.test.server/article/${it.id}"
it.content // return the content
}
}
}
}